import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;


@SuppressWarnings("unchecked")
public class PlanificadorPedidos extends Thread {

	private final String idPlanificador;
	private boolean prendido = true;
	private GUIPlanificador guiPlanificador;
	
	/*
	 * Niveles del Planificador:
	 * 		- 1er nivel: Array de 4 de largo.
	 * 		- 2do nivel: Colas infinitas (dependen de 
	 * 						cuantos pedidos tengan).
	 * 		- 3er nivel: Son los pedidos.
	 */
	private Queue<Pedido>[] nivelesPlanif = new Queue[4];
	
	/*
	 * Lista para controlar los tiempos de los pedidos.
	 */
	private LinkedList<TiempoPedido> tiemposPedidos = 
		new LinkedList<TiempoPedido>(); 
	
	private Semaphore combos = new Semaphore(0);
	
	public PlanificadorPedidos(String idPlanificador, 
			GUIPlanificador guiPlanificador) {
		this.idPlanificador = idPlanificador;
		this.guiPlanificador = guiPlanificador;
		
		for (int i = 0; i < 4; i++)
		{
			/*
			 * Crea las colas en los diferentes
			 * niveles del planificador.
			 */
			Queue<Pedido> colaCombos = new LinkedList<Pedido>();
			nivelesPlanif[i] = colaCombos;
		}
	}
	
	public String getIdPlanificador() {
		return idPlanificador;
	}
	
	public Semaphore getCombos() {
		return combos;
	}

	public void apagar() {
		while (combos.hasQueuedThreads())
		{
			combos.release();
		}
		this.prendido = false;
	}

	/**
	 * Verifica que los pedidos no esperen por
	 * mucho tiempo sin ser atendidos debido a
	 * que su prioridad es baja.
	 */
	public void run()
	{
		while (prendido)
		{
			while (estaVacio() == false)
			{
				if (aumentoPrioridad() == true)
				{
					retroalimentar();
				}
			}
		}
	}
	
	/**
	 * Inserta los pedidos en el planificador y lo planifica.
	 * Dependiendo el tipo de pedido, en que nivel del planificador
	 * lo inserta.
	 * 
	 * @param pedido	El pedido a planificar
	 */
	public void planificarPedido(Pedido pedido){
		try {
			synchronized (this) {
				if (pedido.getCajero() == 3)
				{
					if (pedido.isEspecial() == false)
					{
						nivelesPlanif[0].add(pedido);
					} else {
						nivelesPlanif[1].add(pedido);
					}
				} else {
					if (pedido.isEspecial() == false)
					{
						nivelesPlanif[2].add(pedido);
					} else {
						nivelesPlanif[3].add(pedido);
					}
				}
				/*
				 * Controla el tiempo que espera el pedido
				 * sin ser atendido.
				 */
				Tiempo t = new Tiempo();
				t.setName("TiempoPedido" + pedido.getId());
				TiempoPedido tP = new TiempoPedido(t, pedido);
				tP.getTiempo().start();
				
				tiemposPedidos.add(tP);
				
				guiPlanificador.cargarPlanificador(this.nivelesPlanif);
			}
			
			combos.release();	//Avisa que ya hay combos en el 
								// planificador para que se puedan
								// asignar a los cocineros.
			
		} catch (Exception e) {
		}
	}
	
	/**
	 * Devuelve el pedido de mayor prioridad, es decir
	 * el que se encuentra en la primer cola.
	 * En caso de no haber pedidos en esa cola, busca el 
	 * siguiente pedido de mayor prioridad.
	 * 
	 * PRECONDICION: El planificador NO se encuentra
	 * vacio.
	 * 
	 * @return	Un pedido
	 */
	public Pedido cocinarPedido(){
		try {
			combos.acquire();	//Si no hay combos en el planificador
								// se bloquea hasta que haya.
			
			synchronized (this) {
				Pedido p;
				int pos;
				if (nivelesPlanif[0].isEmpty())
				{
					int i = 1;
					while (i < nivelesPlanif.length)
					{
						if (!nivelesPlanif[i].isEmpty())
						{
							p = nivelesPlanif[i].remove();
							pos = buscarPosPedido(p);
							tiemposPedidos.remove(pos).pararTiempo();
							
							guiPlanificador.cargarPlanificador(this.nivelesPlanif);
							
							return p;
						}
						i++;
					}
				} else {
					p = nivelesPlanif[0].remove();
					pos = buscarPosPedido(p);
					tiemposPedidos.remove(pos).pararTiempo();
	
					guiPlanificador.cargarPlanificador(this.nivelesPlanif);
					
					return p;
				}
			}
		} catch (Exception e) {
		}
		return null;
	}
	
	/**
	 * Verifica si el planificador tiene algun pedido adentro.
	 * 
	 * @return true si esta vacio.
	 */
	private synchronized boolean estaVacio()
	{
		boolean vacio = true;
		int i = 0;
		while ((vacio == true) && (i < nivelesPlanif.length))
		{
			if(!nivelesPlanif[i].isEmpty())
			{
				vacio = false;
			}
			i++;
		}
		return vacio;
	}
	
	/**
	 * A los pedidos que se les aumento la prioridad
	 * se los coloca en una cola con mayor prioridad.
	 */
	private synchronized void retroalimentar()
	{
		for (int i = 1; i < nivelesPlanif.length; i++)
		{
			Object[] pedidosNivel = nivelesPlanif[i].toArray();
			nivelesPlanif[i].removeAll(nivelesPlanif[i]);
			for (int j = 0; j < pedidosNivel.length; j++)
			{
				if (((Pedido)pedidosNivel[j]).getEnvejecimiento() == 0)
				{
					nivelesPlanif[i].offer((Pedido)pedidosNivel[j]);
				} else {
					((Pedido)pedidosNivel[j]).setEnvejecimiento(0);
					nivelesPlanif[i-1].offer((Pedido)pedidosNivel[j]);
					int pos = buscarPosPedido((Pedido)pedidosNivel[j]);
					tiemposPedidos.get(pos).resetTiempo();
				}
			}
		}
		guiPlanificador.cargarPlanificador(this.nivelesPlanif);
	}
	
	/**
	 * Se le aumenta la prioridad a los pedidos que
	 * hayan estado esperando mas de cierto tiempo
	 * 
	 * @return	True si se aumento alguna prioridad
	 */
	private synchronized boolean aumentoPrioridad ()
	{
		boolean aumento = false;
		for (TiempoPedido tp: tiemposPedidos)
		{
			if (tp.getTiempo().getSegundos() > 1)
			{
				Pedido pedido = tp.getPedido();
				
				for (int i = 1; i < nivelesPlanif.length; i++)
				{
					if (nivelesPlanif[i].contains(pedido))
					{
						for (Pedido p: nivelesPlanif[i])
						{
							if (p.equals(pedido))
							{
								p.setEnvejecimiento(1);
								aumento = true;
							}
						}
					}
				}
			}
		}
		return aumento;
	}
	
	/**
	 * Busca en que posicion de la lista de tiempos de 
	 * los pedidos se encuentra determinado pedido.
	 * 
	 * @param pedido	El pedido que se desea conocer su posicion
	 * @return			La posicion del pedido
	 */
	private int buscarPosPedido(Pedido pedido)
	{
		int i = -1;
		for (i = 0; i < tiemposPedidos.size(); i++)
		{
			if (tiemposPedidos.get(i).getIdPedido() == pedido.getId())
			{
				return i;
			}
		}
		return i;
	}
}
